#include<iostream>
#include<cmath>
#include<cstdio>
#include<string>
#include<cstdlib>
#include<fstream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef unsigned char BYTE;
typedef unsigned unsigned short WORD;
typedef unsigned unsigned long DWORD;
typedef long LONG;
#define   WIDTHBYTES(bits) (((bits)+31)/32*4)

#pragma pack(1)
//调色板与颜色表
typedef struct RGBQUAD {
	BYTE rgbBlue;
	BYTE rgbGreen;
	BYTE rgbRed;
	BYTE rgbReserved;
}BGRA;
//位图文件头，为14个字节
typedef struct tagBITMAPFILEHEADER {
	WORD   bfType;
	DWORD  bfSize;               //文件大小
	WORD   bfReserved1; //保留字，不考虑
	WORD   bfReserved2; //保留字，同上
	DWORD bfOffBits;         //实际位图数据的偏移字节数，即前三个部分长度之和
} BmpFileHeader;
//位图信息头，为40个字节
typedef struct tagBITMAPINFOHEADER {
	DWORD   biSize;                  //指定此结构体的长度，为40
	LONG    biWidth;        //位图宽
	LONG    biHeight;       //位图高
	WORD    biPlanes;        //平面数，为1
	WORD    biBitCount;       //采用颜色位数，可以是1，2，4，8，16，24，新的可以是32
	DWORD   biCompression;           //压缩方式，可以是0，1，2，其中0表示不压缩
	DWORD   biSizeImage;             //实际位图数据占用的字节数
	LONG    biXPelsPerMeter;   //X方向分辨率
	LONG    biYPelsPerMeter;   //Y方向分辨率
	DWORD   biClrUsed;                  //使用的颜色数，如果为0，则表示默认值(2^颜色位数)
	DWORD   biClrImportant;             //重要颜色数，如果为0，则表示所有颜色都是重要的
} BmpInfoHeader;

//bmp图像类
class BmpImage {
private:
	BmpFileHeader Fheader;//图像文件头
	BmpInfoHeader Iheader;//图像信息头
	RGBQUAD** palette;//调色板
	BGRA** bgra;//24/32位用的rgba颜色
	long width;//宽度
	long height;//高度
	WORD depth;//深度
	BYTE* idata;//存储图像的数据
	int l_width;//补齐宽度
	long grayhist[256];//灰度直方图数组
public:
	explicit BmpImage(char* filename)//构造函数
	{
		if (!readBmp(filename))
		{
			printf("Failed to create the bmpimage %s\n", filename);
		}
		else {
			printf("Succeed to create the bmpimage %s\n", filename);
		}

	}
	bool readBmp(char* filename)
	{
		ifstream infile(filename, ios::binary);
		// 检查文件是否打开
		if (!infile.is_open()) {
			printf("Failed to open file %s\n", filename);
			return false;
		}
		// 读取BmpFileHeader
		infile.read((char*)(&Fheader), sizeof(BmpFileHeader));
		// 判断是否是bmp图像
		if (Fheader.bfType != 0x4d42) // 0x4d42 = 'BM'
		{
			printf("File %s is not bmp file\n", filename);
			return false;
		}
		// 读取BmpInfoHeader
		infile.read((char*)(&Iheader), sizeof(BmpInfoHeader));
		// 判断是否有调色板，如果有就读取调色板数据
		if (Fheader.bfOffBits == 54) {//说明文件头和信息头的大小就是偏移量，没有调色板
			palette = nullptr;
		}
		else {
			// 读取调色板数据,24/32位颜色表赋空值
			infile.read((char*)(palette), Fheader.bfOffBits - 54);
			bgra = nullptr;
		}
		// 给属性赋值
		width = Iheader.biWidth;
		height = Iheader.biHeight;
		depth = Iheader.biBitCount;
		memset(rgbarray, 0, 4);
		// 读取图像数据
		// 申请内存空间并初始化为0
		l_width = WIDTHBYTES(width * depth);
		long datasize = height * l_width;
		idata = new BYTE[datasize];
		memset(idata, 0, datasize);
		// 读取图像数据到内存
		infile.read((char*)idata, datasize);
		//图像是24或32位时，运行rgba()函数，将像素的rgba值保存到数组bgra中
		if (depth == 32 || depth == 24) {
			rgba();
		}
		infile.close();
		return true;
	}
	//如果图像是32/24位，求图像RGBA值
	bool rgba() {
		if (depth != 24 && depth != 32)
		{
			printf("The image is not 24bit or 32bit\n");
			return false;
		}
		//补齐宽度
		l_width = WIDTHBYTES(width * depth);
		bgra = NULL;
		long i, j;
		bgra = new BGRA * [height];
		for (i = 0; i < height; i++)
			bgra[i] = new BGRA[width];
		int k;
		int index = 0;
		if (depth == 32)
			for (i = 0; i < height; i++)
			{
				int k0 = (height - i - 1) * l_width;
				for (j = 0; j < width; j++)
				{
					k = k0 + (j * 4);
					bgra[i][j].rgbRed = idata[k + 2];
					bgra[i][j].rgbGreen = idata[k + 1];
					bgra[i][j].rgbBlue = idata[k];
					bgra[i][j].rgbReserved = idata[k + 3];
				}
			}
		if (depth == 24)
			for (i = 0; i < height; i++)
			{
				int k0 = (height - i - 1) * l_width;
				for (j = 0; j < width; j++)
				{
					k = k0 + (j * 3);
					bgra[i][j].rgbRed = idata[k + 2];
					bgra[i][j].rgbGreen = idata[k + 1];
					bgra[i][j].rgbBlue = idata[k];
					bgra[i][j].rgbReserved = 0;
				}
			}
		return true;
	}
	//显示bmp文件头属性
	void showBmpFileHead()
	{
		printf("BmpFileHead：\n");
		printf("Filetype:%x\n", Fheader.bfType);
		printf("Filesize:%d\n", Fheader.bfSize);
		printf("Reserved1:%d\n", Fheader.bfReserved1);
		printf("Reserved2:%d\n", Fheader.bfReserved2);
		printf("Offbits:%d\n", Fheader.bfOffBits);
	}
	//显示bmp信息头属性
	void showBmpInfoHead()
	{
		printf("BmpInfoHead：\n");
		printf("Width:%d\n", Iheader.biWidth);
		printf("Height:%d\n", Iheader.biHeight);
		printf("Planes:%d\n", Iheader.biPlanes);
		printf("Bitcount:%d\n", Iheader.biBitCount);
		printf("Compression:%d\n", Iheader.biCompression);
		printf("Sizeimage:%d\n", Iheader.biSizeImage);
		printf("X-pixels per meter:%d\n", Iheader.biXPelsPerMeter);
		printf("Y-pixels per meter:%d\n", Iheader.biYPelsPerMeter);
		printf("Colors used:%d\n", Iheader.biClrUsed);
		printf("Important colors used:%d\n", Iheader.biClrImportant);
	}
	//将图像32位保存到指定文件
	bool saveBmp(char* filename)
	{
		long i, j;
		ofstream outfile(filename, ios::binary);
		Iheader.biBitCount = 32;
		outfile.write((char*)& Fheader, sizeof(Fheader));
		outfile.write((char*)& Iheader, sizeof(Iheader));
		int lwidth = WIDTHBYTES(width * 32) - width * 4;//计算为确保位图数据区的实际宽度为32字节的整数倍需添加的0字节个数
		for (i = 0; i < height; i++)
		{
			for (j = 0; j < width; j++)
			{
				outfile.write((char*)(&bgra[height - i - 1][j]), 4);

			}
			BYTE tmp = 0;
			for (j = 0; j < lwidth; j++)
				outfile.write((char*)tmp, sizeof(tmp));

		}
		cout << "Succeed to save bmpfile on " << filename << endl;
		outfile.close();
		return true;
	}
	BYTE rgbarray[4];//暂时存储某一个像素的rgba颜色表的四个成员，方便运算,并且需要将其公有化方便调用
	//将某个像素的rgba颜色表放在一个数组中，方便运算
	void addArray(long i, long j)
	{
		memset(rgbarray, 0, 4);
		rgbarray[0] = bgra[i][j].rgbRed;
		rgbarray[1] = bgra[i][j].rgbGreen;
		rgbarray[2] = bgra[i][j].rgbBlue;
		rgbarray[3] = bgra[i][j].rgbReserved;
	}
	//处理某个像素在图像运算之后的溢出问题
	void clearOverflow()
	{
		int i = 0;
		for (i = 0; i < 4; ++i)
		{
			if (rgbarray[i] > 255)
				rgbarray[i] = 255;
			if (rgbarray[i] < 0)
				rgbarray[i] = 0;
		}
	}
	//将某个像素的颜色数组释放到颜色表中，用来更改成运算后的结果
	void popArray(long i, long j)
	{
		bgra[i][j].rgbRed = rgbarray[0];
		bgra[i][j].rgbGreen = rgbarray[1];
		bgra[i][j].rgbBlue = rgbarray[2];
		bgra[i][j].rgbReserved = rgbarray[3];
		memset(rgbarray, 0, 4);
	}
	//rgb图像转灰度图像
	void convertRGB2Gray()
	{
		double gray;
		for (long i = 0; i < height; i++)
		{
			for (long j = 0; j < width; j++)
			{
				gray = 0.299 * bgra[i][j].rgbRed + 0.587 * bgra[i][j].rgbGreen + 0.114 * bgra[i][j].rgbBlue;
				bgra[i][j].rgbRed = (BYTE)gray;
				bgra[i][j].rgbGreen = (BYTE)gray;
				bgra[i][j].rgbBlue = (BYTE)gray;
			}
		}
		printf("Succeed to convert rgb to gray\n");
		char src[] = "rgb2gray.bmp";
		saveBmp(src);
	}
	//生成灰度直方图
	void saveHist()
	{
		memset(grayhist, 0, 256);
		convertRGB2Gray();
		int id;
		for (int i = 0; i < height; i++) {
			for (int j = 0; j < width; j++) {
				id=bgra[i][j].rgbBlue;
				grayhist[id]++;
			}
		}
	}
	//----------第4.1题 位面分割----
	//按位将灰度图像分成八张
	void split()
	{   //转灰度
		convertRGB2Gray();
		long i, j;
		int k = 0, l = 0;
		//分割
		for (k = 0; k <= 7; ++k)
		{
			for (i = 0; i < height; ++i)
			{
				for (j = 0; j < width; ++j)
				{
					addArray(i, j);
					if (rgbarray[0] & (1 << k))
					{
						for (l = 0; l < 4; ++l)
						{
							rgbarray[l] = 255;
						}
						popArray(i, j);
					}
					else {
						for (l = 0; l < 4; ++l)
						{
							rgbarray[l] = 0;
						}
						popArray(i, j);
					}
				}
			}
			char str[100];
			sprintf_s(str, "split-%d.bmp", k);
			printf("Split %d image on %s\n", k, str);
			saveBmp(str);
			rgba();
		}
	}
	//----------第4.2题 线性对比度展宽/动态范围调整----
	//参数是灰度范围上限fb下限fa,从fa-fb扩展到0-255
	void adjustLinear(int fa, int fb)
	{   //转灰度
		convertRGB2Gray();
		long i, j;
		int f, g, k;
		for (i = 0; i < height; ++i)
		{
			for (j = 0; j < width; ++j)
			{
				addArray(i, j);
				f = rgbarray[0];
				if (f <= fb && f >= fa)
				{
					g = (f - fa) * 255 / (fb - fa);
				}
				if (f < fa)
				{
					g = 0;
				}
				if (f > fb)
				{
					g = 255;
				}
				for (k = 0; k < 4; ++k)
				{
					rgbarray[k] = g;
				}
				popArray(i, j);
			}
		}
		char str[] = "adjustlinear.bmp";
		printf("Succeed to linear-adjust on %s from %d to %d\n", str, fa, fb);
		saveBmp(str);
		rgba();
	}
	//----------第4.3题 非线性对比度展宽/动态范围调整-----
	//参数是增益系数c
	void adjustUnlinear(unsigned int c)
	{
		//转灰度
		convertRGB2Gray();
		long i, j;
		int f, g, k;
		for (i = 0; i < height; ++i)
		{
			for (j = 0; j < width; ++j)
			{
				addArray(i, j);
				f = rgbarray[0];
				g = (int)c * log(double(1.0 + f));
				for (k = 0; k < 4; ++k)
				{
					rgbarray[k] = g;
				}
				clearOverflow();
				popArray(i, j);
			}
		}
		char str[] = "adjustunlinear.bmp";
		printf("Succeed to unlinear-adjust on %s and the ratio is %d\n", str, c);
		saveBmp(str);
		rgba();
	}
	//----------第4.4题 直方图均衡化-----
	void equalizeGrayhist()
	{   //储存直方图
		saveHist();
		long sum = width * height;
		//概率和累计概率
		double pr[256], prsum[256];
		int grayequal[256];
		memset(grayequal, 0, 256);
		int i;
		long j, k;
		for (i = 0; i < 256; ++i)
		{
			pr[i] = ((double)grayhist[i] / sum);
		}
		prsum[0] = pr[0];
		for (i = 1; i < 256; ++i)
		{
			prsum[i] = prsum[i - 1] + pr[i];
		}
		//更新均衡化后灰度值
		for (i = 0; i < 256; ++i)
		{
			grayequal[i] = (long)(255 * prsum[i] + 0.5);
		}
		BYTE gray;
		for (j = 0; j < height; ++j)
		{
			for (k = 0; k < width; ++k)
			{
				addArray(j, k);
				gray = grayequal[rgbarray[0]];
				for (i = 0; i < 4; ++i)
				{
					rgbarray[i] = gray;
				}
				popArray(j, k);
			}
		}
		char str[] = "grayequal.bmp";
		printf("Succeed to equalize gray hist on %s\n", str);
		saveBmp(str);
		rgba();
	}
	//----------第4.5题 伪彩色增强-----
	//利用灰度转化法变化彩色
	void convertGray2RGB()
	{
		convertRGB2Gray();
		long i, j;
		int k;
		BYTE gr;
		for (i = 0; i < height; ++i)
		{
			for (j = 0; j < width; ++j)
			{
				addArray(i, j);
				gr = rgbarray[0];
				if (gr < 64)
				{
					rgbarray[0] = 0;
					rgbarray[1] = 4 * gr;
					rgbarray[2] = 255;
				}
				else if (gr < 128)
				{
					rgbarray[0] = 0;
					rgbarray[1] = 255;
					rgbarray[2] = -4 * gr + 512 - 1;
				}
				else if (gr < 192)
				{
					rgbarray[0] = 4 * gr - 512;
					rgbarray[1] = 255;
					rgbarray[2] = 0;
				}
				else
				{
					rgbarray[0] = 255;
					rgbarray[1] = -4 * gr + 1024 - 1;
					rgbarray[2] = 0;
				}
				popArray(i, j);
			}
		}
		char str[] = "gray2rgb.bmp";
		printf("Succeed to convert gray to RGB on %s\n", str);
		saveBmp(str);
		rgba();
	}
};